home *** CD-ROM | disk | FTP | other *** search
- Subject: v18i022: Rename multiple files
- Newsgroups: comp.sources.unix
- Sender: sources
- Approved: rsalz@uunet.UU.NET
-
- Submitted-by: Vladimir Lanin <lanin@csd2.nyu.edu>
- Posting-number: Volume 18, Issue 22
- Archive-name: rename-files
-
- This must have been done already by someone somewhere, but I could not
- find it, so here goes.
-
- Whereas mv can rename (as opposed to move) only one file at a time, the
- following program (ren) can rename many files according to search and
- replacement patterns, ala VMS (but better). ren checks for replacement
- name collisions and handles rename chains gracefully.
-
- To compile, just unshar the following and cc ren.c. ren.1 contains the
- man style doc.
-
- I have not tried this under anything but bsd 4.2 and 4.3, but I don't see
- why there should be a problem.
-
- Vladimir Lanin
- lanin@csd2.nyu.edu
-
- Any comments appreciated.
-
- --cut here--
- #! /bin/sh
- # This is a shell archive, meaning:
- # 1. Remove everything above the #! /bin/sh line.
- # 2. Save the resulting text in a file.
- # 3. Execute the file with /bin/sh (not csh) to create:
- # Makefile (spliced in by hand)
- # ren.1
- # ren.c
- export PATH; PATH=/bin:/usr/bin:$PATH
- echo shar: 'extracting Makefile'
- if test -f Makefile ; then
- echo shar: will not clobber existing Makefile
- else
- sed 's/^X//' <<\SHAR_EOF >Makefile
- Xall: ren ren.1
- X
- Xren: ren.c
- X $(CC) -o ren $(CFLAGS) ren.c
- Xinstall: all
- X @echo "install according to local convention"
- Xclean:
- X rm -f core a.out ren ren.o
- SHAR_EOF
- fi
- echo shar: "extracting 'ren.1'" '(3843 characters)'
- if test -f 'ren.1'
- then
- echo shar: "will not over-write existing file 'ren.1'"
- else
- sed 's/^X//' << \SHAR_EOF > 'ren.1'
- X.TH REN 1 "May 20, 1988"
- X.UC 4
- X.SH NAME
- Xren \- rename multiple files
- X.SH SYNOPSIS
- X.B ren
- X[
- X.B \-d
- X|
- X.B \-k
- X|
- X.B \-a
- X] [
- X.B \-v
- X] [path/]search replacement
- X.SH DESCRIPTION
- X.I Ren
- Xrenames each file in the current directory
- X(or in the
- X.I path
- Xdirectory, if specified)
- Xthat matches the
- X.I search
- Xpattern;
- Xeach matching file's new name is given by the
- X.I replacement
- Xpattern.
- XThe multiple rename is performed safely,
- Xi.e. without any unexpected deletion of files
- Xdue to collisions of replacement names with existing names,
- Xor with other replacement names.
- XAlso, all error checking is done prior to doing any renames,
- Xso either all renames are done, or none.
- X.PP
- XThe search pattern is a filename
- Xwith embedded wildcards, i.e. * and ?,
- Xwhich have their usual meanings of, respectively,
- Xmatching any string of characters,
- Xand matching any single character.
- XThe replacement pattern is another filename
- Xwith embedded
- X.I wildcard
- X.IR indexes ,
- Xeach of which consists of the character # followed by a digit
- Xfrom 1 to 9.
- XIn the new name of a matching file,
- Xthe wildcard indexes are replaced by the
- Xactual characters that matched the referenced wildcards
- Xin the original filename.
- XThus, if the search pattern is "abc*.*.*"
- Xand the replacement pattern is "xyz#1.#3",
- Xthen "abc.txt.doc" is renamed to "xyz.doc"
- X(since the first * matched "", the second matched "txt",
- Xand the third matched "doc").
- X.PP
- XNote that the shell normally expands the wildcards * and ?,
- Xwhich in the case of
- X.I ren
- Xis undesirable.
- XThus, in most cases it is necessary to enclose the search pattern
- Xin quotes, e.g.
- Xren "*.a" #1.b.
- XTo strip any of the characters *, ?, and # of their special meaning to
- X.I ren,
- Xas when the actual replacement name must contain the character #,
- Xprecede the special character with \\
- X(and enclose the argument in qoutes because of the shell).
- X.PP
- XNote that a path is not allowed in the replacement pattern.
- X.I Ren
- Xdoes not allow moving files between directories,
- Xwhich facilitates the safety checks next described.
- X.PP
- XWhen any two matching files
- Xwould have to be renamed to the same new filename,
- X.I ren
- Xdetects the condition prior to doing any renames
- Xand aborts with an error message.
- X.pp
- X.I Ren
- Xalso checks if any file deletes would result from the rename,
- Xi.e. if some file1 would have to be renamed to file2,
- Xand file2 already exists and is not itself being renamed.
- X(Here and below, "delete" really means "unlink".)
- XIn such a case,
- X.I ren
- Xasks you (by reading a line from standard input)
- Xif you really wish file2 to be deleted.
- XIf your answer is negative, file1 is not renamed.
- X.PP
- X.I Ren
- Xsafely performs chain renames,
- Xi.e. when file1 is renamed to file2,
- Xfile2 to file3, file3 to file4, etc,
- Xby doing the renames in the proper order.
- XIn the case that the chain is a cycle, i.e. filen is renamed back to file1,
- X.I ren
- Xbreaks the cycle by using a temporary name.
- X.PP
- XFiles beginning with . are not matched against the search pattern
- X(and thus not renamed)
- Xunless the search pattern explicitly begins with '.'.
- XIn any case, "." and ".." are never matched.
- X.PP
- XOptions:
- X.TP
- X.I \-v
- X(verbose):
- Xafter each rename,
- Xthe message "file1 -> file2 [(*)]" appears on the standard output.
- XThe (*) appears in the case of a deleting rename,
- Xi.e. when the old file2 is deleted.
- X.TP
- X.IR \-d ,
- X.IR \-k ,
- X.IR \-a :
- Xsuppress interrogation with regard to deleting renames, and:
- X.TP
- X.I \-d
- X(delete): perform all deleting renames silently.
- X.TP
- X.I \-k
- X(keep): perform no deleting renames.
- X.TP
- X.I \-a
- X(abort): if any deleting renames are detected,
- Xabort prior to doing any renames.
- X.SH "SEE ALSO"
- Xmv(1)
- X.SH "AUTHOR"
- XVladimir Lanin
- X.br
- Xlanin@csd2.nyu.edu
- X.SH "BUGS"
- XIf the search pattern is not quoted,
- Xthe shell expands the wildcards.
- X.I Ren
- Xthen complains that there are too many arguments
- X(if indeed there are),
- Xbut can not determine that the lack of quotes is the cause.
- SHAR_EOF
- if test 3843 -ne "`wc -c < 'ren.1'`"
- then
- echo shar: "error transmitting 'ren.1'" '(should have been 3843 characters)'
- fi
- fi
- echo shar: "extracting 'ren.c'" '(11365 characters)'
- if test -f 'ren.c'
- then
- echo shar: "will not over-write existing file 'ren.c'"
- else
- sed 's/^X//' << \SHAR_EOF > 'ren.c'
- X#include <stdio.h>
- X#include <sys/file.h>
- X#include <sys/types.h>
- X#include <sys/dir.h>
- X
- X#define MAXWILD 20
- X#define MAXREP 40
- X
- Xtypedef struct rep{
- X int ftorep;
- X char *repname;
- X struct rep *dofirst;
- X int mustdel;
- X int status;
- X struct rep *nextrep;
- X} REP;
- X
- X#define DO 0
- X#define FORGET 1
- X#define DONE 2
- X
- X#define ASKDEL 0
- X#define QUIETDEL 1
- X#define NODEL 2
- X#define ABORTDEL 3
- X
- Xextern int alphasort();
- Xextern int scandir();
- Xextern qsort();
- X
- Xstatic procargs();
- Xstatic int procdir();
- Xstatic int checkpats();
- Xstatic int getreps();
- Xstatic int scan();
- Xstatic char *makerep();
- Xstatic checkcollisons();
- Xstatic int mycmp();
- Xstatic checkdeletes();
- Xstatic int bsearch();
- Xstatic char** breakcycles();
- Xstatic dorenames();
- X
- Xstatic char tempprefix[] = "renTMP";
- X
- Xmain(argc, argv)
- X int argc;
- X char *(argv[]);
- X{
- X char *from_pat, *to_pat, *path;
- X int verbose, delstyle;
- X int nfils, nreps;
- X struct direct **dot;
- X REP *reps, **filrep;
- X char **tempnames;
- X
- X procargs(argc, argv, &verbose, &delstyle, &from_pat, &to_pat, &path);
- X checkpats(from_pat, to_pat);
- X nfils = procdir(path, &dot, &filrep);
- X nreps = getreps(dot, nfils, from_pat, to_pat, filrep, &reps);
- X checkcollisons(reps, nreps);
- X checkdeletes(reps, dot, filrep, nfils, delstyle);
- X tempnames = breakcycles(reps, nreps, dot);
- X dorenames(reps, dot, tempnames, verbose);
- X return(0);
- X}
- X
- X
- Xstatic procargs(argc, argv, pverbose, pdelstyle, pfrom_pat, pto_pat, ppath)
- X int argc;
- X char *(argv[]);
- X int *pverbose, *pdelstyle;
- X char **pfrom_pat, **pto_pat, **ppath;
- X{
- X char *p;
- X int arg1;
- X
- X *pverbose = 0;
- X *pdelstyle = ASKDEL;
- X for (arg1 = 1; arg1 < argc && *(argv[arg1]) == '-'; arg1++)
- X for (p = argv[arg1]+1; *p != '\0'; p++)
- X if (*p == 'v')
- X *pverbose = 1;
- X else if (*p == 'd')
- X *pdelstyle = QUIETDEL;
- X else if (*p == 'k')
- X *pdelstyle = NODEL;
- X else if (*p == 'a')
- X *pdelstyle = ABORTDEL;
- X else {
- X fputs("Illegal option -", stderr);
- X fputc(*p, stderr);
- X fputc('\n', stderr);
- X exit(1);
- X }
- X
- X if (argc - arg1 != 2) {
- X fputs(
- X "Usage: ren [-d|-k|-a] [-v] [path/]search_pattern replacement_pattern\n",
- X stderr);
- X fputs("\nSearch patterns containing wildcard(s) should be quoted.\n",
- X stderr);
- X fputs("Put #n into the replacement pattern to insert the string\n",
- X stderr);
- X fputs("matched by the n'th wildcard in the search pattern.\n", stderr);
- X exit(1);
- X }
- X
- X *ppath = argv[arg1];
- X *pto_pat = argv[arg1 + 1];
- X for (
- X *pfrom_pat = *ppath + strlen(*ppath);
- X **pfrom_pat != '/' && *pfrom_pat != *ppath;
- X --(*pfrom_pat)
- X ) {}
- X if (**pfrom_pat == '/') {
- X **pfrom_pat = '\0';
- X if (*pfrom_pat == *ppath)
- X *ppath = "/";
- X (*pfrom_pat)++;
- X }
- X else
- X *ppath = ".";
- X}
- X
- X
- Xstatic checkpats(from_pat, to_pat)
- X char *from_pat, *to_pat;
- X{
- X char *p;
- X int nwilds;
- X
- X for (nwilds = 0, p = from_pat; *p != '\0'; p++) {
- X if (*p == '\\') {
- X p++;
- X if (*p == '\0')
- X break;
- X }
- X else if (*p == '*' || *p == '?')
- X nwilds++;
- X }
- X
- X for (p = to_pat; *p != '\0'; p++) {
- X if (*p == '/') {
- X fputs("The replacement pattern must not contain a path.\n",
- X stderr);
- X exit(1);
- X }
- X else if (*p == '*' || *p == '?') {
- X fputs("No wildcards allowed in replacement pattern.\n", stderr);
- X fputs("Use #n to insert the substring matched\n", stderr);
- X fputs("by the n'th wildcard in the search pattern.\n", stderr);
- X exit(1);
- X }
- X else if (*p == '#') {
- X if (*(p+1) < '1' || *(p+1) > '9' || *(p+1) - '0' > nwilds) {
- X fputc('#', stderr);
- X fputc(*(p+1), stderr);
- X fputs(": bad substring numer.\n", stderr);
- X fputs("(Use '\\#' to get '#' in replacement string.)\n", stderr);
- X exit(1);
- X }
- X p++;
- X }
- X else if (*p == '\\') {
- X p++;
- X if (*p == '\0')
- X break;
- X }
- X }
- X}
- X
- X
- Xstatic int procdir(path, pdot, pfilrep)
- X char *path;
- X struct direct ***pdot;
- X REP ***pfilrep;
- X{
- X int nfils;
- X
- X if (access(path, R_OK | X_OK | W_OK) < 0) {
- X fputs("Read/write access denied to ", stderr);
- X fputs(path, stderr);
- X fputc('\n', stderr);
- X exit(1);
- X }
- X if (chdir(path) < 0) {
- X fputs("Strange, can not change directory to ", stderr);
- X fputs(path, stderr);
- X fputc('\n', stderr);
- X exit(1);
- X }
- X if ((nfils = scandir(".", pdot, NULL, alphasort)) < 0) {
- X fputs("Strange, can not scan ", stderr);
- X fputs(path, stderr);
- X fputc('\n');
- X exit(1);
- X }
- X *pfilrep = (REP **)malloc(nfils * sizeof(REP *));
- X return(nfils);
- X}
- X
- X
- X
- Xstatic int getreps(dot, nfils, from_pat, to_pat, filrep, preps)
- X struct direct *(dot[]);
- X int nfils;
- X char *from_pat, *to_pat;
- X REP *(filrep[]);
- X REP **preps;
- X{
- X char *(start[MAXWILD]);
- X int len[MAXWILD];
- X int nreps, i;
- X REP *cur;
- X
- X for (*preps = NULL, nreps = 0, i = 0; i < nfils; i++) {
- X if (
- X (*(dot[i]->d_name) != '.' || *from_pat == '.') &&
- X strcmp(dot[i]->d_name, ".") != 0 &&
- X strcmp(dot[i]->d_name, "..") != 0 &&
- X scan(from_pat, dot[i]->d_name, start, len)
- X ) {
- X cur = (REP *)malloc(sizeof(REP));
- X filrep[i] = cur;
- X cur->repname = makerep(to_pat, start, len);
- X cur->ftorep = i;
- X cur->mustdel = -1;
- X cur->nextrep = *preps;
- X cur->status = DO;
- X *preps = cur;
- X nreps++;
- X if (*(cur->repname) == '\0') {
- X fputc('\'', stderr);
- X fputs(dot[i]->d_name, stderr);
- X fputs("' would have to be renamed to empty string.\n",
- X stderr);
- X fputs("Aborting, no renames done.\n");
- X exit(1);
- X }
- X }
- X else
- X filrep[i] = NULL;
- X }
- X if (nreps == 0) {
- X fputs("No match.\n", stderr);
- X exit(1);
- X }
- X return(nreps);
- X}
- X
- X
- Xstatic int scan(pat, s, start1, len1)
- X char *pat, *s, **start1;
- X int *len1;
- X{
- X *start1 = 0;
- X while (1) {
- X if (*pat == '*') {
- X pat++;
- X *start1 = s;
- X if (*pat == '\0') {
- X *len1 = strlen(s);
- X return(1);
- X }
- X else {
- X for (*len1=0; !scan(pat, s, start1+1, len1+1); (*len1)++, s++)
- X if (*s == '\0')
- X return(0);
- X return(1);
- X }
- X }
- X else if (*pat == '\0')
- X return(*s == '\0');
- X else if (*pat == '?') {
- X if (*s == '\0')
- X return(0);
- X *(start1++) = s;
- X *(len1++) = 1;
- X pat++;
- X s++;
- X }
- X else {
- X if (*pat == '\\') {
- X pat++;
- X if (*pat == '\0')
- X return(*s == '\0');
- X }
- X if (*pat == *s) {
- X pat++;
- X s++;
- X }
- X else
- X return(0);
- X }
- X }
- X}
- X
- X
- Xstatic char *makerep(pat, start, len)
- X char *pat, **start;
- X int *len;
- X{
- X int i, k;
- X char *q, *p, *res;
- X char b[MAXNAMLEN];
- X
- X p = b;
- X for ( ; *pat != '\0'; pat++) {
- X if (*pat == '#') {
- X pat++;
- X k = *pat - '1';
- X if (p - b + len[k] > MAXNAMLEN) {
- X fputs("Replacement name too long.\n", stderr);
- X exit(1);
- X }
- X for (i=0, q = start[k]; i < len[k]; i++)
- X *(p++)= *(q++);
- X }
- X else {
- X if (*pat == '\\') {
- X pat++;
- X if (*pat == '\0')
- X break;
- X }
- X if (p - b + 1 > MAXNAMLEN) {
- X fputs("Replacement name too long.\n", stderr);
- X exit(1);
- X }
- X *(p++)= *pat;
- X }
- X }
- X *(p++) = '\0';
- X res = (char *)malloc((p - b) * sizeof(char));
- X strcpy(res, b);
- X return(res);
- X}
- X
- X
- Xstatic checkcollisons(reps, nreps)
- X REP *reps;
- X int nreps;
- X{
- X char **repdict;
- X REP *cur;
- X int i;
- X
- X repdict = (char **)malloc(nreps * sizeof(char *));
- X for (i = 0, cur = reps; cur != NULL; cur = cur->nextrep)
- X repdict[i++] = cur->repname;
- X qsort(repdict, nreps, sizeof(char *), mycmp);
- X for (i = 0; i < nreps-1; i++)
- X if (strcmp(repdict[i], repdict[i+1]) == 0) {
- X fputs("Two or more files would have to be renamed to '",
- X stderr);
- X fputs(repdict[i], stderr);
- X fputs("'.\n", stderr);
- X fputs("Aborting, no renames done.\n", stderr);
- X exit(1);
- X }
- X}
- X
- X
- Xstatic int mycmp(pps1, pps2)
- X char **pps1, **pps2;
- X{
- X return(strcmp(*pps1, *pps2));
- X}
- X
- X
- Xstatic checkdeletes(reps, dot, filrep, nfils, delstyle)
- X REP *reps;
- X struct direct *(dot[]);
- X REP *(filrep[]);
- X int nfils, delstyle;
- X{
- X int recheck, i;
- X REP *cur;
- X char doit, reply[MAXREP];
- X
- X do {
- X recheck = 0;
- X for (cur = reps; cur != NULL; cur = cur->nextrep) {
- X if (cur->status == FORGET)
- X continue;
- X if ((i = bsearch(cur->repname, dot, nfils)) >= 0) {
- X if (filrep[i] == NULL && cur->mustdel < 0) {
- X cur->dofirst = NULL;
- X if (delstyle == QUIETDEL)
- X cur->mustdel = i;
- X else if (delstyle == NODEL) {
- X cur->status = FORGET;
- X filrep[cur->ftorep] = NULL;
- X recheck = 1;
- X }
- X else if (delstyle == ABORTDEL) {
- X fputs(dot[i]->d_name, stderr);
- X fputs(" would have to be removed.\n", stderr);
- X fputs("Aborting, no renames done.\n", stderr);
- X exit(1);
- X }
- X else {
- X fputs(dot[cur->ftorep]->d_name, stderr);
- X fputs(" -> ", stderr);
- X fputs(cur->repname, stderr);
- X fputs(" ; remove old ", stderr);
- X fputs(dot[i]->d_name, stderr);
- X fputs("? ", stderr);
- X for (;;) {
- X doit = *fgets(reply, MAXREP, stdin);
- X if (doit == 'Y' || doit == 'y') {
- X cur->mustdel = i;
- X break;
- X }
- X else if (doit == 'N' || doit == 'n') {
- X cur->status = FORGET;
- X filrep[cur->ftorep] = NULL;
- X recheck = 1;
- X break;
- X }
- X else
- X fputs("Yes or No? ", stderr);
- X }
- X }
- X }
- X else {
- X cur->dofirst = filrep[i];
- X cur->mustdel = -1;
- X }
- X }
- X else {
- X cur->dofirst = NULL;
- X cur->mustdel = -1;
- X }
- X }
- X } while (recheck);
- X}
- X
- X
- Xstatic int bsearch(s, dlist, n)
- X char *s;
- X struct direct *(dlist[]);
- X int n;
- X{
- X int first, k, last, res;
- X
- X for(first = 0, last = n - 1;;) {
- X if (last < first)
- X return(-1);
- X k = (first + last) >> 1;
- X if ((res = strcmp(s, dlist[k]->d_name)) == 0)
- X return(k);
- X if (res < 0)
- X last = k - 1;
- X else
- X first = k + 1;
- X }
- X}
- X
- X
- Xstatic char** breakcycles(reps, nreps, dot)
- X REP *reps;
- X int nreps;
- X struct direct *(dot[]);
- X{
- X char **tempnames;
- X int tempno;
- X REP *cur, *pred;
- X
- X tempnames = (char **)malloc(nreps * sizeof(char *) + 1);
- X tempno = 0;
- X for (cur = reps; cur != NULL; cur = cur->nextrep) {
- X if (cur->status == FORGET)
- X continue;
- X for (pred = cur->dofirst;
- X pred != NULL && pred != cur;
- X pred = pred->dofirst)
- X {
- X if (pred->status != DO) {
- X fputs("Strange error in cycle checking.\n", stderr);
- X exit(1);
- X }
- X }
- X if (pred == cur)
- X if (cur->dofirst == cur)
- X cur->status = FORGET;
- X else {
- X pred = (REP *)malloc(sizeof(REP));
- X tempnames[++tempno] =
- X (char *)malloc(sizeof(tempprefix) + strlen(cur->repname));
- X strcpy(tempnames[tempno], tempprefix);
- X strcat(tempnames[tempno], cur->repname);
- X pred->repname = cur->repname;
- X pred->ftorep = -tempno;
- X pred->dofirst = cur->dofirst;
- X pred->mustdel = -1;
- X pred->status = DO;
- X pred->nextrep = cur->nextrep;
- X cur->repname = tempnames[tempno];
- X cur->dofirst = NULL;
- X cur->nextrep = pred;
- X }
- X }
- X return(tempnames);
- X}
- X
- X
- Xstatic dorenames(reps, dot, tempnames, verbose)
- X REP *reps;
- X struct direct *(dot[]);
- X char *(tempnames[]);
- X int verbose;
- X{
- X REP *cur;
- X int skipped;
- X char *fromname;
- X
- X do {
- X skipped = 0;
- X for (cur = reps; cur != NULL; cur = cur->nextrep) {
- X if (cur->status == DO) {
- X if (cur->dofirst != NULL && cur->dofirst->status != DONE)
- X skipped = 1;
- X else {
- X fromname = (cur->ftorep < 0 ?
- X tempnames[-(cur->ftorep)] :
- X dot[cur->ftorep]->d_name);
- X cur->status = DONE;
- X if (rename(fromname, cur->repname)) {
- X fputs("Strange. Can not rename '", stderr);
- X fputs(fromname, stderr);
- X fputs("' to '", stderr);
- X fputs(cur->repname, stderr);
- X fputs("'\n", stderr);
- X }
- X else if (verbose) {
- X fputs(fromname, stdout);
- X fputs(" -> ", stdout);
- X fputs(cur->repname, stdout);
- X if (cur->mustdel >= 0)
- X fputs(" (*)", stderr);
- X fputc('\n', stdout);
- X }
- X }
- X }
- X }
- X } while (skipped);
- X}
- SHAR_EOF
- if test 11365 -ne "`wc -c < 'ren.c'`"
- then
- echo shar: "error transmitting 'ren.c'" '(should have been 11365 characters)'
- fi
- fi
- # End of shell archive
- exit 0
-
-